home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Snippets / BeachBall (fat) / BeachBall.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-05-29  |  14.7 KB  |  535 lines  |  [TEXT/KAHL]

  1. /******************************************************************
  2.  
  3. BeachBall.c
  4. Routines for asynchronous spinning beach-ball cursor
  5.  
  6. Based on & expanded from a code sample in Scott Knaster's excellent
  7. book, "Macintosh Programming Secrets", full of tons of truly
  8. useful & informative stuff.
  9.  
  10.  
  11. **************
  12.  
  13. ©1993 Peter Vanags, Electronic Ink; portions ©1992 Scott Knaster & Keith Rollin
  14. All rights reserved.
  15.  
  16. Distributed by:
  17.  
  18. Electronic Ink
  19. 72 Caselli Avenue
  20. San Francisco, CA 94114
  21.  
  22. AppleLink: INK.LINK
  23. CompuServe: 70401,3202
  24. Internet: ink.link@applelink.apple.com
  25.  
  26. Modified 1997 by Karl Bunker:
  27. * 1 bug fixed (the AcurHandle wasn't being locked)
  28. * Updated to allow PPC native compiling
  29. * References to low-memory globals removed
  30. * IsBeachBallOn() routine added
  31. Karl Bunker
  32. KarlBunker@aol.com
  33. http://users.aol.com/karlbunker/
  34.  
  35. ***************
  36.  
  37. ABOUT THE BEACH BALL ROUTINES
  38.  
  39. These routines can be used either synchronously (without the VBL task)
  40. or asynchronously. To use them in their intended manner (async):
  41.  
  42.     StartBeachBall (bbAcurID,TRUE,0); //starts the beach ball spinning
  43.     
  44.     ...complete whatever processes you need to...
  45.     
  46.     ReverseBeachBall (); //reverses the spin direction
  47.     
  48.     ...complete some more processes....
  49.     
  50.     StopBeachBall(); //stops the beach ball spinning
  51.     
  52.  
  53. Additionally, you can set the third argument to StartBeachBall to
  54. a non-zero value (say, 300 for 5 seconds' timeOut) and the beach
  55. ball will stop spinning after that many ticks have elapsed:
  56.  
  57.     #define timeOut 300    //set a standard timeout of 300 ticks (5 secs)
  58.     
  59.     StartBeachBall (bbAcurID,TRUE,timeOut); //starts the beach ball spinning
  60.     
  61.     ...do something...
  62.     
  63.     KeepSpinning (timeOut); //reset the timeOut counter
  64.     
  65.     ...do some more stuff...
  66.     
  67.     StopBeachBall(); //stops the beach ball spinning
  68.     
  69.  
  70. This is to prevent the beach ball from spinning merrily away as your
  71. program hangs.
  72.  
  73.  
  74.  
  75. To use the BeachBall routines synchronously, call them this way:
  76.  
  77.     StartBeachBall (bbAcurID,FALSE,0); //sets up the beach ball structures
  78.     
  79.     for (c=0;c<limit;c++) {     //run some type of loop
  80.  
  81.         SpinBeachBall();        //call this as often as you like
  82.         
  83.         ...do something...
  84.     
  85.     }
  86.     
  87.     StopBeachBall (); disposes of the beach ball structures
  88.     
  89.     
  90. ******************************************************************/
  91.  
  92.  
  93. #include <retrace.h>
  94. #include "BeachBall.h"
  95.  
  96.         
  97. //timing values for beach ball
  98. #define bbArrowTime        10        //ticks before we switch to a watch
  99. #define bbWatchTime        90        //ticks before the watch becomes a beachball
  100. #define bbSpinTime         8        //ticks between beachball spins
  101. #define bbRetryTime        5        //time to wait before re-trying (when LMGetCrsrBusy() is true)
  102. #define bbSuspendTime    50        //time between re-checks while spinning is suspended
  103.  
  104.  
  105. typedef struct {
  106.     unsigned short    numCursors;
  107.     unsigned short    index;
  108.     CursHandle        cursors[];
  109. } Acur,*AcurPtr,**AcurHandle;
  110.  
  111. typedef struct {
  112.     VBLTask            theTask;            //VBL task record, empty if we're not using a VBL task
  113.     long            vblCountDown;        //tickCount at which we'll stop spinning
  114.     long            a5;                    //the a5 value of the current application
  115.     short            cursorPhase;        //phase of the cursor: suspended, arrow, watch or beachball
  116.     short            spinIncrement;        //value is either 1 or -1, to determine which way the ball spins
  117.     AcurHandle        theAcur;            //handle to the animated cursor
  118.     CursHandle        watchHandle;        //handle to the watch cursor, since we can't move memory in VBL
  119.     GrafPtr            startWindow;        //the front window when we started
  120. } BeachBallTask,*BeachBallTaskPtr;
  121.  
  122. //cursor phases
  123. enum {suspendedPhase,arrowPhase,watchPhase,bbPhase};
  124.  
  125. //some utility routines
  126. static AcurHandle InitAcur (short resID);
  127. static void LockAcur (AcurHandle theAcur);
  128. static void UnlockAcur (AcurHandle theAcur);
  129. static void DisposeAcur (AcurHandle theAcur);
  130. static void DoTheSpin( BeachBallTaskPtr theBBTask, Boolean calledFromVBL );
  131.  
  132. #if GENERATINGCFM
  133.     // Under CFM (i.e., PPC compile), we're passed the VBL task pointer
  134. static void SpinVBLTask(BeachBallTaskPtr theBBTask);
  135. #else
  136.     // Otherwise, we'll have to get it out of register A0 
  137. static void SpinVBLTask(void);                    
  138. #endif
  139.  
  140. //two global variables
  141. static BeachBallTaskPtr    bbTask = nil;
  142. static long    lastTicks = 0;
  143.  
  144.  
  145.  
  146. /******************************************************************
  147.  
  148. StartBeachBall sets things rolling. Call this routine
  149. with vblInstall set to TRUE at the start of a long
  150. operation to have automatic, asynchronous user feedback
  151. about the operation.
  152.  
  153. if vblInstall is FALSE, you'll nead to call SpinBeachBall()
  154. repeatedly from your routines to keep the ball rolling.
  155.  
  156. vblTimeOut is measured in ticks. If vblInstall is TRUE, and
  157. vblTimeOut is non-zero, KeepSpinning() must be called before the
  158. number of ticks in vblTimeOut have elapsed.
  159.  
  160. If KeepSpinning() is not called, the cursor stops spinning
  161. (freezes in whatever state it's in). This is so the cursor doesn't
  162. keep spinning merrily away when your program hangs. If you want
  163. to be sloppy (like me) or your programs never hang (not like me),
  164. set vblTimeOut to 0, and don't worry about making calls to
  165. KeepSpinning(). 
  166.  
  167. The beachball routine automatically adapts to the duration of your
  168. operations. Very short operations (<10 ticks) will not switch cursors.
  169. Operations up to about 2 seconds will get a wristwatch.
  170. After about 2 seconds, the cursor will change to a spinning beach ball.
  171. So to be safe, bracket ANY operation that might take a long time with
  172. calls to StartBeachBall and StopBeachBall. Remember that an
  173. instantaneous operation on the Quadra you program with
  174. might take a few seconds or longer on a Mac Classic!
  175.  
  176. ******************************************************************/
  177.  
  178. OSErr StartBeachBall (short acurID, Boolean vblInstall, long vblTimeOut) 
  179. {
  180.     if ( bbTask != NULL )
  181.         return( noErr );
  182.     
  183.     bbTask = (BeachBallTaskPtr)NewPtrClear( sizeof(BeachBallTask) );
  184.     if ( bbTask != NULL )
  185.     {
  186.         //initialize the generic fields
  187.         
  188.         bbTask->cursorPhase = arrowPhase;
  189.         bbTask->spinIncrement = 1;
  190.         bbTask->watchHandle = GetCursor(watchCursor);
  191.         HLockHi( (Handle)bbTask->watchHandle );
  192.         bbTask->startWindow = FrontWindow();
  193.         
  194.         if (vblTimeOut)
  195.             bbTask->vblCountDown = TickCount() + vblTimeOut;
  196.         else
  197.             bbTask->vblCountDown = 0;
  198.  
  199.         if (!(bbTask->theAcur = InitAcur (acurID)))  
  200.         {
  201.             DisposePtr ((Ptr)bbTask);
  202.             bbTask = nil;
  203.             return ResError();
  204.         }
  205.         LockAcur( bbTask->theAcur );
  206.         bbTask->theTask.qType = vType;
  207.         bbTask->theTask.vblAddr = NewVBLProc((ProcPtr)SpinVBLTask);
  208.         bbTask->theTask.vblCount = bbArrowTime;
  209.         bbTask->theTask.vblPhase = 0;
  210.         bbTask->a5 = (long) LMGetCurrentA5();
  211.  
  212.         if (vblInstall)
  213.             VInstall ((QElemPtr) bbTask);
  214.         else    
  215.             lastTicks = TickCount(); //set up a global so we can manually decrement
  216.     }
  217.     else
  218.         return MemError();
  219.  
  220.     return noErr;
  221. }
  222.  
  223.  
  224. /******************************************************************
  225.  
  226. KeepSpinning() keeps the beachball spinning if vblTimeOut is non-zero.
  227.  
  228. ******************************************************************/
  229.  
  230. void KeepSpinning (long vblTimeOut) 
  231. {
  232.     if ( bbTask != NULL )
  233.     {
  234.         if (bbTask->vblCountDown)
  235.             bbTask->vblCountDown = TickCount() + vblTimeOut;
  236.     }
  237. }
  238.  
  239. /******************************************************************
  240.  
  241. IsBeachBallOn is useful if a piece of time-consuming code might be
  242. called several times in quick succession (as when processing a batch
  243. of files). Instead of the code trying to determine whether it's being
  244. called for the first time this session, just do this:
  245. if ( IsBeachBallOn() )
  246.     KeepSpinning( timeOut );
  247. else
  248.     StartBeachBall( bbAcurID,TRUE,timeOut );
  249.     
  250. ******************************************************************/
  251. Boolean    IsBeachBallOn( void )
  252. {
  253.     if ( bbTask != NULL )
  254.         return( true );
  255.     else
  256.         return( false );
  257. }
  258.  
  259. /******************************************************************
  260.  
  261. ReverseBeachBall() spins the beach ball the other way.
  262.  
  263. ******************************************************************/
  264.  
  265. void ReverseBeachBall (void) 
  266. {
  267.     bbTask->spinIncrement *= -1;
  268. }
  269.  
  270.  
  271. /******************************************************************
  272.  
  273. SuspendBeachBall and ResumeBeachBall are macros to optimize the 
  274. VBL task's performance
  275.  
  276. ******************************************************************/
  277.  
  278. #define SUSPEND_BEACH_BALL(theBBTask)  \
  279.     if (theBBTask && theBBTask->cursorPhase) { \
  280.         theBBTask->cursorPhase = suspendedPhase; \
  281.         theBBTask->theTask.vblCount = bbSuspendTime; \
  282.         SetCursor (&qd.arrow); \
  283.     } \
  284.  
  285.  
  286. #define RESUME_BEACH_BALL(theBBTask)  \
  287.     if (theBBTask && !theBBTask->cursorPhase) { \
  288.         theBBTask->cursorPhase = bbPhase; \
  289.         theBBTask->theTask.vblCount = bbSpinTime; \
  290.     } \
  291.  
  292.  
  293. /******************************************************************
  294.  
  295. SpinVBLTask is called as a VBL task, so under 68K compiles we
  296. have to make sure to set the a5 world properly.
  297. Under PPC compiles, the VBL task pointer is nicely handed to us,
  298. making life somewhat easier. This means that two versions of the 
  299. same routine are required, however.
  300.  
  301. ******************************************************************/
  302.  
  303. #if !GENERATINGCFM
  304. extern    BeachBallTaskPtr GetVBLRec(void)
  305.     = 0x2008;    /* MOVE.L    A0,D0 */
  306.  
  307. static void SpinVBLTask (void) 
  308. {
  309.     long                oldA5;
  310.     BeachBallTaskPtr    theBBTask;
  311.     
  312.     //this assembly code puts a0 (which points to our VBLTask record)
  313.     //into a local variable, so we can access our beach ball data
  314. /*
  315.     asm {
  316.         move.l A0,theBBTask
  317.     }
  318. */
  319.     theBBTask = GetVBLRec();
  320.     
  321.     oldA5 = SetA5 (theBBTask->a5);
  322.     
  323.     DoTheSpin( theBBTask, true );
  324.     
  325.     SetA5 (oldA5);
  326. }
  327. #else
  328. static void SpinVBLTask(BeachBallTaskPtr theBBTask)
  329. {
  330.     DoTheSpin( theBBTask, true );
  331. }
  332. #endif
  333.  
  334.  
  335. /******************************************************************
  336.  
  337. SpinBeachBall can be called from your own routines
  338. repeatedly, if you prefer not to use the VBL task.
  339.  
  340. ******************************************************************/
  341.  
  342. void SpinBeachBall( void )
  343. {
  344.     DoTheSpin( bbTask, false );
  345. }
  346.  
  347.  
  348. /******************************************************************
  349.  
  350. DoTheSpin does most of the actual work.
  351.  
  352. In addition to changing the cursor when it's time to do so,
  353. DoTheSpin checks the status of the mouse button and the front
  354. window. If the mouse button is down, or the front window
  355. is not the same window as it was at the time StartBeachBall
  356. was called (and that window is a dialog-type), beach ball spinning
  357. will be suspended.
  358.  
  359. While the beachball is spinning, we need to keep on the lookout
  360. for alert dialogs and mouseclicks. In both instances, we need
  361. to suspend the beach ball spinning until the dialog is
  362. dismissed or the mouse button is released. These actions mainly
  363. apply to the VBL usage.
  364.  
  365. If you use SpinCursor to "manually" spin the cursor, be sure to set 
  366. the cursor to an arrow when you put up alert dialogs. You don't have 
  367. to worry about this if you're using the VBL.
  368.  
  369. ******************************************************************/
  370.  
  371. static void DoTheSpin( BeachBallTaskPtr theBBTask, Boolean calledFromVBL ) 
  372. {
  373.     GrafPtr                currentWindow;
  374.     
  375.     
  376.     if ( !calledFromVBL )
  377.     {
  378.         //Manually decrement & check the VBL count
  379.         if ((theBBTask->theTask.vblCount -= (TickCount() - lastTicks)) > 0) 
  380.         {
  381.             lastTicks = TickCount();
  382.             return;
  383.         }
  384.     }
  385.  
  386.     if (!LMGetCrsrBusy())  //make sure we can change the cursor now
  387.     {
  388.         //check if the timeOut has elapsed
  389.         if (theBBTask->vblCountDown && (TickCount() > theBBTask->vblCountDown)) 
  390.         {
  391.             theBBTask->theTask.vblCount = bbSuspendTime;
  392.             return;
  393.         }
  394.             
  395.         //check if we should suspend beach ball spinning
  396.         if ((Button() && theBBTask->cursorPhase) ||
  397.             (((currentWindow=FrontWindow()) != theBBTask->startWindow) &&
  398.             (currentWindow && (((WindowPeek)currentWindow)->windowKind == dialogKind)))) 
  399.         {
  400.             SUSPEND_BEACH_BALL(theBBTask);
  401.         }
  402.         else if (!Button() && !theBBTask->cursorPhase)
  403.             RESUME_BEACH_BALL(theBBTask);
  404.  
  405.         if (theBBTask->cursorPhase) 
  406.         {
  407.             switch (theBBTask->cursorPhase) 
  408.             {
  409.                 case arrowPhase:
  410.                     SetCursor(*theBBTask->watchHandle);
  411.                     theBBTask->theTask.vblCount = bbWatchTime;
  412.                     theBBTask->cursorPhase = watchPhase;
  413.                     break;
  414.                 case watchPhase:
  415.                     //set the phase & fall thru to the default action
  416.                     theBBTask->cursorPhase = bbPhase;
  417.                 default:
  418.                     (**(theBBTask->theAcur)).index += theBBTask->spinIncrement;
  419.                     (**(theBBTask->theAcur)).index %= (**(theBBTask->theAcur)).numCursors;
  420.                     //the next two lines turn negative increments around, but leave positive increments alone
  421.                     (**(theBBTask->theAcur)).index += (**(theBBTask->theAcur)).numCursors;
  422.                     (**(theBBTask->theAcur)).index %= (**(theBBTask->theAcur)).numCursors;
  423.                     SetCursor (*(**(theBBTask->theAcur)).cursors[(**(theBBTask->theAcur)).index]);
  424.                     theBBTask->theTask.vblCount = bbSpinTime;
  425.                     break;
  426.             }
  427.         }
  428.         else 
  429.             theBBTask->theTask.vblCount = bbSuspendTime;
  430.     }
  431.     else   //the cursor can't be changed now, so try again in a little while...
  432.         theBBTask->theTask.vblCount = bbRetryTime;
  433. }
  434.  
  435.  
  436. /******************************************************************
  437.  
  438. StopBeachBall stops the spinning and disposes of all the
  439. beachball structures
  440.  
  441. ******************************************************************/
  442.  
  443. void StopBeachBall (void) 
  444. {
  445.     if (bbTask) 
  446.     {
  447.         VRemove ((QElemPtr)bbTask);
  448.         HUnlock( (Handle)bbTask->watchHandle );
  449.         UnlockAcur( bbTask->theAcur );
  450.         DisposeAcur (bbTask->theAcur);
  451.         DisposeRoutineDescriptor( bbTask->theTask.vblAddr );
  452.         DisposePtr ((Ptr)bbTask);
  453.         bbTask = nil;
  454.         SetCursor(&qd.arrow);
  455.     }
  456. }
  457.  
  458.  
  459.  
  460. /******************************************************************
  461.  
  462. Animated cursor ('acur') utilities
  463. These utilities deal with loading an 'acur' and its related
  464. 'CURS' resources, locking & unlocking them in memory,
  465. and proper disposal
  466.  
  467. ******************************************************************/
  468.  
  469. static AcurHandle InitAcur (short resID) 
  470. {
  471.     short         cursorCount;
  472.     CursHandle    *workPtr;
  473.     AcurHandle    theAcur = (AcurHandle) GetResource('acur',resID);
  474.     
  475.     if (theAcur) 
  476.     {
  477.         DetachResource ((Handle)theAcur);
  478.         cursorCount = (*theAcur)->numCursors;
  479.         (*theAcur)->index = 0;
  480.         
  481.         HLock((Handle)theAcur);
  482.         workPtr = (*theAcur)->cursors;
  483.         while (cursorCount--)
  484.             if (!(*workPtr++ = (CursHandle) GetResource ('CURS',*(short*)workPtr))) 
  485.             {
  486.                 HUnlock ((Handle)theAcur);
  487.                 DisposeHandle ((Handle)theAcur);
  488.                 return nil;
  489.             }
  490.         HUnlock ((Handle)theAcur);
  491.     }
  492.     return theAcur;
  493. }
  494.  
  495. static void LockAcur (AcurHandle theAcur) 
  496. {
  497.     short        cursorCount;
  498.     CursHandle    *workPtr;
  499.     
  500.     cursorCount = (*theAcur)->numCursors;
  501.     
  502.     HLockHi ((Handle)theAcur);
  503.     workPtr = (*theAcur)->cursors;
  504.     while (cursorCount--)
  505.         HLockHi((Handle)(*workPtr++));
  506. }
  507.  
  508. static void UnlockAcur (AcurHandle theAcur) 
  509. {
  510.     short        cursorCount;
  511.     CursHandle    *workPtr;
  512.     
  513.     cursorCount = (*theAcur)->numCursors;
  514.     
  515.     workPtr = (*theAcur)->cursors;
  516.     while (cursorCount--)
  517.         HUnlock((Handle)(*workPtr++));
  518.     HUnlock ((Handle)theAcur);
  519. }
  520.  
  521. static void DisposeAcur (AcurHandle theAcur) 
  522. {
  523.     short        cursorCount;
  524.     CursHandle    *workPtr;
  525.     
  526.     cursorCount = (*theAcur)->numCursors;
  527.     
  528.     HLock((Handle)theAcur);
  529.     workPtr = (*theAcur)->cursors;
  530.     while (cursorCount--)
  531.         ReleaseResource((Handle)(*workPtr++));
  532.     HUnlock((Handle)theAcur);
  533.     DisposeHandle ((Handle)theAcur);
  534. }
  535.